﻿using CpuSimulator.Instructions;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace CpuSimulator
{
    /// <summary>
    /// 模拟一个Risc-V架构的Cpu
    /// </summary>
    public class Cpu
    {
        public static int PCStartValue = 0;

        public static List<string> rtypes = new List<string>()
            {
                "ADD","AND","OR","SLL","SLT","SLTU","SRA","SRL","SUB","XOR","MUL"
            };
        public static List<string> itypes = new List<string>()
            {
                "ADDI","ANDI","JALR","LB","LBU","LH","LHU","LW","ORI","SLLI","SLTI","SLTIU","SRAI","SRLI","XORI"
            };
        public static List<string> btypes = new List<string>()
            {
                "BEQ","BGE","BGEU","BLT","BLTU","BNE"
            };
        public static List<string> stypes = new List<string>()
            {
                "SB","SH","SW"
            };
        public static List<string> utypes = new List<string>()
            {
                "AUIPC","LUI"
            };
        public static List<string> jtypes = new List<string>()
            {
                "JAL"
            };


        /// <summary>
        /// 寄存器
        /// </summary>
        public List<Register> Registers { get; set; }

        /// <summary>
        /// PC寄存器
        /// </summary>
        public Register PC { get; set; }

        /// <summary>
        /// 是否运行指令中
        /// </summary>
        private bool _running = false;

        /// <summary>
        /// 内存改变事件
        /// </summary>
        public event Action<int, string>? MemoryChange;

        /// <summary>
        /// 寄存器改变事件
        /// </summary>
        public event Action<int, string>? RegisterChange;

        public Dictionary<(int start, int end), Device> AddressMap { get; }

        public List<int> DebugAddress { get; set; } = new List<int>();
        private static List<string> _registerName = new List<string>()
        {
            "zero","ra","sp","gp","tp","t0","t1","t2","s0","s1","a0","a1","a2","a3","a4","a5","a6","a7","s2","s3","s4","s5","s6","s7","s8","s9","s10","s11","t3","t4","t5","t6"
        };
        private Cpu() : this(new Dictionary<(int, int), Device>())
        {
        }

        public Cpu(Dictionary<(int, int), Device> addressMap)
        {
            Registers = new List<Register>(32);
            for (int i = 0; i < 32; i++)
            {
                var r = new Register((short)i, "x" + i);
                r.Name = _registerName[i];
                Registers.Add(r);
            }
            PC = new Register(100, "PC");
            PC.SetValue(PCStartValue);
            AddressMap = addressMap.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);
        }

        /// <summary>
        /// 模拟Cpu启动，清空寄存器，PC指向0
        /// </summary>
        public void Start()
        {
            Registers.ForEach(x => x.Clear());
            PC.Clear();
        }

        /// <summary>
        /// 模拟Cpu开始运行
        /// </summary>
        public void Run()
        {
            _running = true;
            int lastpc = -1;
            Task.Run(async () =>
            {
                int count = 0;
                while (PC.Value < AddressMap.Last().Key.end && _running == true)
                {
                    if (DebugAddress.Contains(PC.Value))
                    {
                        _running = false;
                        break;
                    }
                    RunInstruction();//运行指令
                    RegisterChange?.Invoke(PC.Index, PC.Value.ToString("X8"));//通知前台寄存器发生了变化，只是为了前台显示
                    count++;
                    if (PC.Value == lastpc)
                    {
                        await Task.Delay(1);
                    }
                    lastpc = PC.Value;
                }
            });
        }

        public void SetValue(string RegisterNumber,string RegisterValue)
        {
            byte index = 0;
            int value = 0;
            if (RegisterValue.StartsWith("0x"))
            {
                int.TryParse(RegisterValue[2..], NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out value);
            }
            else
            {
                int.TryParse(RegisterValue, out value);
            }
            Register? r = null;
            if (RegisterNumber?.ToLower() == "pc")
            {
                r = PC;
            }
            else
            {
                parseResister(RegisterNumber, out index);
                r = Registers.Find(x => x.Index == index);
            }
            r.SetValue(value);
            RegisterChange?.Invoke(r.Index, r.Value.ToString("X8"));//通知前台寄存器发生了变化，只是为了前台显示
        }

        public string Export(string addr, string length)
        {
            int value = 0;
            if (addr.StartsWith("0x"))
            {
                int.TryParse(addr[2..], NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out value);
            }
            else
            {
                int.TryParse(addr, out value);
            }
            int ll = 0;
            if (addr.StartsWith("0x"))
            {
                int.TryParse(length[2..], NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out ll);
            }
            else
            {
                int.TryParse(length, out ll);
            }
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < ll; i += 4)
            {
                var ins=  FetchInstruction(value);
                value += 4;
                var valtext = Convert.ToString(ins, 2);
                for (int c = valtext.Length; c < 32; c++)
                {
                    valtext = "0" + valtext;
                }
                sb.Append(valtext);
                var ins2 = DecodeInstruction(ins);//译指
                var comm = ins2?.ToString() ?? "";
                if (string.IsNullOrEmpty(comm)==false)
                {
                    comm = "//" + comm;
                }
                sb.AppendLine(comm);
            }
            return sb.ToString();
        }

        public void SetValue(string input)
        {
            var data = new List<string>();
            var temp = input.Split(',').ToList();
            temp.ForEach(x => data.AddRange(x.Split('\n')));
            foreach (var item in data)
            {
                var txt = item.Replace(" ", "");
                Regex reg = new Regex("(\\w+?)=(-?\\w+)");
                var m = reg.Match(txt);
                if (m.Success)
                {
                    SetValue(m.Groups[1].Value, m.Groups[2].Value);
                }
            }
        }
        public void Debug(string addr)
        {
            if(string.IsNullOrEmpty(addr) == false)
            {
                DebugAddress = new List<int>();
                var ads = addr.Split(',');
                foreach (var item in ads)
                {
                    var str = item.Trim();
                    int pos = -1;
                    if (str.StartsWith("0x"))
                    {
                        int.TryParse(str[2..], NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out pos);
                    }
                    else
                    {
                        int.TryParse(str, out pos);
                    }
                    if(pos > -1)
                    {
                        DebugAddress.Add(pos);
                    }
                }
            }
        }

        /// <summary>
        /// 暂停Cpu运行
        /// </summary>
        public void Pause()
        {
            _running = false;
        }

        /// <summary>
        /// Cpu单步运行
        /// </summary>
        public void RunStep()
        {
            RunInstruction();
            RegisterChange?.Invoke(PC.Index, PC.Value.ToString("X8"));
        }

        private void IncreatePC(Instruction? lastIns)
        {
            //B类型和J类型以及JARL命令都会修改PC，这种指令不需要再让PC+4
            if (lastIns?.InsType == InstructionTypeEnum.B || lastIns?.InsType == InstructionTypeEnum.J || lastIns is JALR)
            {

            }
            else
            {
                PC.SetValue(PC.Value + 4);//PC寄存器指向下一个指令
            }
        }

        /// <summary>
        /// Cpu关闭
        /// </summary>
        public void Shutdown()
        {
            _running = false;
        }

        /// <summary>
        /// Cpu重启
        /// </summary>
        public void Reset()
        {
            _running = false;
            Registers.ForEach(x => x.Clear());//清空寄存器
            PC.Clear();//PC寄存器指向0
        }

        /// <summary>
        /// 运行指令，
        /// </summary>
        private void RunInstruction()
        {
            var instata = FetchInstruction();//取指
            var ins = DecodeInstruction(instata);//译指
            RunInstruction(ins);//执行
            IncreatePC(ins);
        }

        #region 给外部类提供的触发事件的公共函数
        public void InvodeRegisterChange(int index, string value)
        {
            //x0寄存器永远是0，不会改变
            if (index != 0)
            {
                RegisterChange?.Invoke(index, value);
            }
        }

        public void InvodeMemoryChange(int index, string value)
        {
            MemoryChange?.Invoke(index, value);
        }
        #endregion

        private uint FetchInstruction(int? pos = null)
        {
            Device device = new Device(0);
            int deviceStart = 0;
            if(pos == null)
            {
                pos = PC.Value;
            }
            foreach (var item in AddressMap)
            {
                if (pos >= item.Key.start && pos < item.Key.end)
                {
                    device = item.Value;
                    deviceStart = pos.Value - item.Key.start;
                    break;
                }
            }
            if (device != null)
            {
                var rv = BitConverter.ToUInt32(new byte[] { device[deviceStart], device[deviceStart + 1], device[deviceStart + 2], device[deviceStart + 3] }, 0);
                return rv;
            }
            else
            {
                return 0;
            }
        }
        private Instruction? DecodeInstruction(uint data)
        {
            return Instruction.Parse(data);
        }

        private void RunInstruction(Instruction? ins)
        {
            if (ins != null)
            {
                ins.Execute(this);
            }
        }

        public void WriteToAddress(byte[] data, int position = 0)
        {
            Device device = new Device(0);
            int deviceStart = 0;
            foreach (var item in AddressMap)
            {
                if (position >= item.Key.start && PC.Value < item.Key.end)
                {
                    device = item.Value;
                    deviceStart = position - item.Key.start;
                }
            }
            foreach (var item in data)
            {
                device[deviceStart] = item;
                MemoryChange?.Invoke(position, item.ToString("X2"));
                position++;
                deviceStart++;
            }
        }

        public string TranslateCmd(string cmd, string rs1, string rs2, string rd, string imm)
        {
            InstructionTypeEnum? insType = null;
            if (rtypes.Contains(cmd))
            {
                insType = InstructionTypeEnum.R;
            }
            else if (itypes.Contains(cmd))
            {
                insType = InstructionTypeEnum.I;
            }
            else if (btypes.Contains(cmd))
            {
                insType = InstructionTypeEnum.B;
            }
            else if (stypes.Contains(cmd))
            {
                insType = InstructionTypeEnum.S;
            }
            else if (utypes.Contains(cmd))
            {
                insType = InstructionTypeEnum.U;
            }
            else if (jtypes.Contains(cmd))
            {
                insType = InstructionTypeEnum.J;
            }

            string func7 = "0000000";
            string func3 = "";
            string op = "";
            string rv = "";
            byte rs1value = 0;
            byte rs2value = 0;
            byte rdvalue = 0;
            int immvalue = 0;
            switch (insType)
            {
                case InstructionTypeEnum.R:
                    op = "0110011";
                    if(cmd == "SUB" || cmd == "SRA")
                    {
                        func7 = "0100000";
                    }
                    if(cmd == "MUL")
                    {
                        func7 = "0000001";
                    }
                    switch (cmd)
                    {
                        case "ADD":
                        case "MUL":
                        case "SUB":
                            func3 = "000";
                            break;
                        case "SLL":
                            func3 = "001";
                            break;
                        case "SLT":
                            func3 = "010";
                            break;
                        case "SLTU":
                            func3 = "011";
                            break;
                        case "XOR":
                            func3 = "100";
                            break;
                        case "SRL":
                        case "SRA":
                            func3 = "101";
                            break;
                        case "OR":
                            func3 = "110";
                            break;
                        case "AND":
                            func3 = "111";
                            break;
                        default:
                            break;
                    }
                    if(parseResister(rs1,out rs1value) == false)
                    {
                        rv = "//rs1格式错误，请输入0-31之间的数字";
                    }
                    if (parseResister(rs2, out rs2value) == false)
                    {
                        rv = "//rs2格式错误，请输入0-31之间的数字";
                    }
                    if (parseResister(rd, out rdvalue) == false)
                    {
                        rv = "//rd格式错误，请输入0-31之间的数字";
                    }
                    if (rv == "")
                    {
                        rv = $"{func7}{getRegStr(rs2value)}{getRegStr(rs1value)}{func3}{getRegStr(rdvalue)}{op}";
                        rv += $"//{cmd.ToLower()} x{rdvalue},x{rs1value},x{rs2value}; {BinaryStringToHexString(rv)}";
                    }
                    break;

                case InstructionTypeEnum.I:
                    op = "0010011";
                    if (cmd == "SRAI")
                    {
                        func7 = "0100000";
                    }
                    switch (cmd)
                    {
                        case "ADDI":
                            func3 = "000";
                            break;
                        case "SLLI":
                            func3 = "001";
                            break;
                        case "SLTI":
                            func3 = "010";
                            break;
                        case "SLTIU":
                            func3 = "011";
                            break;
                        case "XORI":
                            func3 = "100";
                            break;
                        case "SRLI":
                        case "SRAI":
                            func3 = "101";
                            break;
                        case "ORI":
                            func3 = "110";
                            break;
                        case "ANDI":
                            func3 = "111";
                            break;
                        case "LB":
                            func3 = "000";
                            op = "0000011";
                            break;
                        case "LH":
                            func3 = "001";
                            op = "0000011";
                            break;
                        case "LW":
                            func3 = "010";
                            op = "0000011";
                            break;
                        case "LBU":
                            func3 = "100";
                            op = "0000011";
                            break;
                        case "LHU":
                            func3 = "101";
                            op = "0000011";
                            break;
                        case "JALR":
                            func3 = "000";
                            op = "1100111";
                            break;

                        default:
                            break;
                    }
                    if (parseResister(rs1, out rs1value) == false)
                    {
                        rv = "//rs1格式错误，请输入0-31之间的数字";
                    }
                    if (parseResister(rd, out rdvalue) == false)
                    {
                        rv = "//rd格式错误，请输入0-31之间的数字";
                    }
                    if (parseImm(imm, out immvalue) == false)
                    {
                        rv = "//imm格式错误，请输入一个数字";
                    }
                    if (rv == "")
                    {
                        if (cmd == "SLLI" || cmd == "SRLI" || cmd == "SRAI")
                        {
                            rv = $"{func7}{getImmStr(immvalue, 12)[^5..]}{getRegStr(rs1value)}{func3}{getRegStr(rdvalue)}{op}";
                        }
                        else
                        {
                            rv = $"{getImmStr(immvalue, 12)}{getRegStr(rs1value)}{func3}{getRegStr(rdvalue)}{op}";
                        }
                        if (cmd.StartsWith("L") || cmd.Equals("JALR"))
                        {
                            rv += $"//{cmd.ToLower()} x{rdvalue},{imm}(x{rs1value}); {BinaryStringToHexString(rv)}";
                        }
                        else
                        {
                            rv += $"//{cmd.ToLower()} x{rdvalue},x{rs1value},{imm}; {BinaryStringToHexString(rv)}";
                        }
                    }
                    break;

                case InstructionTypeEnum.B:
                    op = "1100011";
                    switch (cmd)
                    {
                        case "BEQ":
                            func3 = "000";
                            break;
                        case "BNE":
                            func3 = "001";
                            break;
                        case "BLT":
                            func3 = "100";
                            break;
                        case "BGE":
                            func3 = "101";
                            break;
                        case "BLTU":
                            func3 = "110";
                            break;
                        case "BGEU":
                            func3 = "111";
                            break;
                        default:
                            break;
                    }
                    if (parseResister(rs1, out rs1value) == false)
                    {
                        rv = "//rs1格式错误，请输入0-31之间的数字";
                    }
                    if (parseResister(rs2, out rs2value) == false)
                    {
                        rv = "//rs2格式错误，请输入0-31之间的数字";
                    }
                    if (parseImm(imm, out immvalue) == false)
                    {
                        rv = "//imm格式错误，请输入一个数字";
                    }
                    if (rv == "")
                    {
                        var ims = getImmStr(immvalue, 12) + "0";
                        rv = $"{ims[0]}{ims[2..8]}{getRegStr(rs2value)}{getRegStr(rs1value)}{func3}{ims[8..12]}{ims[1]}{op}";
                        rv += $"//{cmd.ToLower()} x{rs1value},x{rs2value},{imm}; {BinaryStringToHexString(rv)}";
                    }
                    break;
                case InstructionTypeEnum.S:
                    op = "0100011";
                    switch (cmd)
                    {
                        case "SB":
                            func3 = "000";
                            break;
                        case "SH":
                            func3 = "001";
                            break;
                        case "SW":
                            func3 = "010";
                            break;
                        default:
                            break;
                    }
                    if (parseResister(rs1, out rs1value) == false)
                    {
                        rv = "//rs1格式错误，请输入0-31之间的数字";
                    }
                    if (parseResister(rs2, out rs2value) == false)
                    {
                        rv = "//rs2格式错误，请输入0-31之间的数字";
                    }
                    if (parseImm(imm, out immvalue) == false)
                    {
                        rv = "//imm格式错误，请输入一个数字";
                    }
                    if (rv == "")
                    {
                        var ims = getImmStr(immvalue, 12) ;
                        rv = $"{ims[0..7]}{getRegStr(rs2value)}{getRegStr(rs1value)}{func3}{ims[7..12]}{op}";
                        rv += $"//{cmd.ToLower()} x{rs2value},{imm}(x{rs1value}); {BinaryStringToHexString(rv)}";
                    }
                    break;
                case InstructionTypeEnum.U:
                    switch (cmd)
                    {
                        case "AUIPC":
                            op = "0010111";
                            break;
                        case "LUI":
                            op = "0110111";
                            break;
                        default:
                            break;
                    }
                    if (parseResister(rd, out rdvalue) == false)
                    {
                        rv = "//rd格式错误，请输入0-31之间的数字";
                    }
                    if (parseImm(imm, out immvalue) == false)
                    {
                        rv = "//imm格式错误，请输入一个数字";
                    }
                    if (rv == "")
                    {
                        var ims = getImmStr(immvalue, 20);
                        rv = $"{ims}{getRegStr(rdvalue)}{op}";
                        rv += $"//{cmd.ToLower()} x{rdvalue},{imm}; {BinaryStringToHexString(rv)}";
                    }
                    break;
                case InstructionTypeEnum.J:
                    op = "1101111";
                    if (parseResister(rd, out rdvalue) == false)
                    {
                        rv = "//rd格式错误，请输入0-31之间的数字";
                    }
                    if (parseImm(imm,out immvalue) == false)
                    {
                        rv = "//imm格式错误，请输入一个数字";
                    }
                    if (rv == "")
                    {
                        var ims = getImmStr(immvalue, 20)+"0";
                        rv = $"{ims[0]}{ims[10..20]}{ims[9]}{ims[1..9]}{getRegStr(rdvalue)}{op}";
                    }
                    rv += $"//{cmd.ToLower()} x{rdvalue},{imm}; {BinaryStringToHexString(rv)}";
                    break;
                default:
                    break;
            }
            return rv;
        }

        public string TranslateText(string text)
        {
            var line = text.Split("\n");
            string rs1;
            string rs2;
            string rd;
            string imm;
            StringBuilder rv = new StringBuilder();
            foreach (var item in line)
            {
                var txt = item.Trim();
                var index = txt.IndexOf(' ');
                string cmd = "";
                string para = "";
                if(index > 0)
                {
                    cmd = txt.Substring(0,index).Trim().ToUpper();
                    para = txt.Substring(index).Replace(" ","");
                }
                if (rtypes.Contains(cmd))
                {
                    Regex r = new Regex("(\\w+?),(\\w+?),(\\w+)");
                    var m = r.Match(para);
                    if (m.Success)
                    {
                        rd = m.Groups[1].Value;
                        rs1 = m.Groups[2].Value;
                        rs2 = m.Groups[3].Value;
                        imm = "";
                        rv.AppendLine(TranslateCmd(cmd, rs1, rs2, rd, imm));
                    }
                }
                else if (itypes.Contains(cmd))
                {
                    Regex r = new Regex("(\\w+?),(\\w+?),(-?\\w+)");
                    var m = r.Match(para);
                    if (m.Success)
                    {
                        rd = m.Groups[1].Value;
                        rs1 = m.Groups[2].Value;
                        rs2 = "";
                        imm = m.Groups[3].Value;
                        rv.AppendLine(TranslateCmd(cmd, rs1, rs2, rd, imm));
                    }
                    else
                    {
                        r = new Regex("(\\w+?),([\\w\\-]+?)\\((\\w+?)\\)");
                        m = r.Match(para);
                        if (m.Success)
                        {
                            rd = m.Groups[1].Value;
                            rs1 = m.Groups[3].Value;
                            rs2 = "";
                            imm = m.Groups[2].Value;
                            rv.AppendLine(TranslateCmd(cmd, rs1, rs2, rd, imm));
                        }

                    }
                }
                else if (btypes.Contains(cmd))
                {
                    Regex r = new Regex("(\\w+?),(\\w+?),(-?\\w+)");
                    var m = r.Match(para);
                    if (m.Success)
                    {
                        rd = "";
                        rs1 = m.Groups[1].Value;
                        rs2 = m.Groups[2].Value;
                        imm = m.Groups[3].Value;
                        rv.AppendLine(TranslateCmd(cmd, rs1, rs2, rd, imm));
                    }
                }
                else if (stypes.Contains(cmd))
                {
                    Regex r = new Regex("(\\w+?),([\\w\\-]+?)\\((\\w+?)\\)");
                    var m = r.Match(para);
                    if (m.Success)
                    {
                        rd = "";
                        rs1 = m.Groups[3].Value;
                        rs2 = m.Groups[1].Value;
                        imm = m.Groups[2].Value;
                        rv.AppendLine(TranslateCmd(cmd, rs1, rs2, rd, imm));
                    }
                }
                else if (utypes.Contains(cmd))
                {
                    Regex r = new Regex("(\\w+?),(-?\\w+)");
                    var m = r.Match(para);
                    if (m.Success)
                    {
                        rd = m.Groups[1].Value;
                        rs1 = "";
                        rs2 = "";
                        imm = m.Groups[2].Value;
                        rv.AppendLine(TranslateCmd(cmd, rs1, rs2, rd, imm));
                    }
                }
                else if (jtypes.Contains(cmd))
                {
                    Regex r = new Regex("(\\w+?),(-?\\w+)");
                    var m = r.Match(para);
                    if (m.Success)
                    {
                        rd = m.Groups[1].Value;
                        rs1 = "";
                        rs2 = "";
                        imm = m.Groups[2].Value;
                        rv.AppendLine(TranslateCmd(cmd, rs1, rs2, rd, imm));
                    }
                }

            }
            return rv.ToString();
        }
        public string Disassembly(int memPos)
        {
            var instata = FetchInstruction(memPos - 3);//取指
            var ins = DecodeInstruction(instata);//译指
            return ins?.ToString() ?? "";
        }
        private bool parseResister(string r, out byte rv)
        {
            rv = 0;
            if (string.IsNullOrEmpty(r))
            {
                return false;
            }
            if(r.StartsWith("x") || r.StartsWith("X"))
            {
                r = r.Substring(1);
            }
            if (byte.TryParse(r, out rv) == false || (rv < 0 || rv > 31))
            {
                int index = _registerName.FindIndex(x => x == r.ToLower());
                if(index >= 0)
                {
                    rv = (byte)index;
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return true;
            }
        }

        private bool parseImm(string imm, out int rv)
        {
            rv = 0;
            if (string.IsNullOrEmpty(imm))
            {
                return false;
            }
            imm = imm.Trim().ToLower();
            if (imm.StartsWith("0x"))
            {
                return int.TryParse(imm[2..], NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo,out rv);
            }
            else
            {
                return int.TryParse(imm, out rv);
            }
        }

        private string BinaryStringToHexString(string line)
        {
            while (line.Length < 32)
            {
                line = "0" + line;
            }
            uint b = 0;
            int index = 0;
            for (int i = line.Length - 1; i >= 0; i--)
            {
                byte ly = 0;
                if (line[i] == '1')
                {
                    ly = 1;
                }
                b += (uint)(Math.Pow(2, index) * ly);
                index++;
            }
            return "[0x"+b.ToString("X8")+"]";
        }
        private string getRegStr(byte regnumber)
        {
            string rv = Convert.ToString(regnumber, 2);
            while (rv.Length < 5)
            {
                rv = "0" + rv;
            }
            return rv;
        }

        private string getImmStr(int imm,int length)
        {
            string rv = Convert.ToString(imm, 2);
            while (rv.Length < length)
            {
                rv = "0" + rv;
            }
            if(rv.Length > length)
            {
                rv = rv[^length..];
            }
            return rv;
        }

    }
}
